/**
 * 
 */
package registnet.courseDemand;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * The schedule class. It keeps the schedule of a room, student, cluster or a
 * course section. The class has a 24 x 6 string matrix corresponding to the 24
 * thirty-minute schedule in a day from 7am to 7pm and 6 days from Monday to
 * Saturday
 * 
 * @author Dexter Ryan Carrasco
 */
public class Schedule implements CourseDemandConstants {

	/** The schedule matrix */
	private String[][] schedule;

	/** Default Constructor of Schedule */
	public Schedule() {
		this.schedule = new String[24][6];
	}

	/**
	 * Adds a schedule to this schedule using the specified days and time
	 * 
	 * @param daysInt
	 * @param timeInt
	 * @param value
	 * @return true if no conflict, false otherwise
	 */
	public boolean addSchedule(final List<Integer> daysInt,
			final int[] timeInt, final String value) {
		boolean hasConflict = false;
		final String[][] tempSked = this.schedule;
		final int startTime = timeInt[0];
		final int endTime = timeInt[1];

		if (endTime > startTime) {
			for (int i = startTime; i <= endTime; i++) {
				for (final Iterator<Integer> dayIter = daysInt.iterator(); dayIter
						.hasNext();) {
					final int day = dayIter.next();
					if (tempSked[i][day] != null) {
						hasConflict = true;
						break;
					} else {
						tempSked[i][day] = value;
					}
				}

				if (hasConflict) {
					break;
				}
			}

			/* if no conflict copy the schedule */
			if (!hasConflict) {
				this.schedule = tempSked;
			} else {
				// System.out.println(value);
				// this.print();
				// System.out.println("%%%%%%%%%%" + tempSked.hashCode() + "!="
				// + this.schedule.hashCode());
			}
		} else {
			System.out.println("ERROR: Time is not valid in addSchedule()");
		}

		return hasConflict;
	}

	/**
	 * Adds the specified schedule to the matrix schedule and checks for
	 * conflicts
	 * 
	 * @param sked
	 * @return true if no conflict, false otherwise
	 */
	public boolean addSchedule(final Schedule sked) {
		final String[][] stringSked = sked.getSchedule();
		final String[][] tempSked = this.schedule; // TODO try to create a real
		// temp
		// sked
		boolean hasConflict = false;

		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 24; j++) {
				if (stringSked[j][i] != null) {
					if (tempSked[j][i] == null) {
						tempSked[j][i] = stringSked[j][i];
					} else {
						hasConflict = true;
						break;
					}
				}
			}

			if (hasConflict) {
				break;
			}
		}

		if (!hasConflict) {
			this.schedule = tempSked;
		} else {
			// System.out.println("%%%%%%%%%%" + tempSked.hashCode() + "!="
			// + this.schedule.hashCode());
			System.out.println("ERROR: Conflict in schedule");
		}

		return hasConflict;
	}

	/**
	 * Adds a schedule using the specified days and time without checking
	 * conflicts
	 * 
	 * @param daysInt
	 * @param timeInt
	 * @param value
	 */
	public void addScheduleWithoutCheck(final List<Integer> daysInt,
			final int[] timeInt, final String value) {
		final int startTime = timeInt[0];
		final int endTime = timeInt[1];

		if (endTime > startTime) {
			for (int i = startTime; i <= endTime; i++) {
				for (final Iterator<Integer> dayIter = daysInt.iterator(); dayIter
						.hasNext();) {
					final int day = dayIter.next();
					this.schedule[i][day] = value;
				}
			}
		} else {
			System.out.println("ERROR: Time is not valid in addSchedule()");
		}
	}

	/**
	 * Copies the specified schedule without checking conflicts
	 * 
	 * @param sked
	 */
	public void copy(final Schedule sked) {
		final String[][] stringSked = sked.getSchedule();

		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 24; j++) {
				if (stringSked[j][i] != null) {
					this.schedule[j][i] = stringSked[j][i];
				}
			}
		}
	}

	/**
	 * Copies the specified string schedule matrix without checking conflicts
	 * 
	 * @param sked
	 */
	public void copy(final String[][] stringSked) {
		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 24; j++) {
				if (stringSked[j][i] != null) {
					this.schedule[j][i] = stringSked[j][i];
				}
			}
		}
	}

	/**
	 * Finds all the available schedule that matches the specified days, time,
	 * and course type. It returns the possible schedule as list of SchdulePair.
	 * You can further add schedule cases for other days, time or course type
	 * 
	 * @param days
	 * @param time
	 * @param courseCode
	 * @param notYet
	 * @param courseType
	 * @return list of SchedulePair
	 */
	public List<SchedulePair> findVacant(final int days, final int time,
			final String courseCode, final List<String> notYet,
			final int courseType) {

		/** List of possible schedules */
		final List<SchedulePair> possibleSkeds = new ArrayList<SchedulePair>();
		List<int[]> timePriorities = null;

		if (courseType != NSTP_COURSES) {
			// for all non-NSTP COURSES

			switch (time) {
				case ONE_HOUR: {
					timePriorities = ONE_HOUR_LEC_WITH_LAB_SKED_PRIORITY;
					break;
				}

				case ONE_HOUR_HALF: {
					timePriorities = ONE_HOUR_HALF_LEC_SKED_PRIORITY;
					break;
				}

				case TWO_HOURS: {
					timePriorities = TWO_HOURS_SKED_PRIORITY;
					break;
				}

				case THREE_HOURS: {
					timePriorities = THREE_HOURS_SKED_PRIORITY;
					break;
				}

				case SIX_HOURS: {
					timePriorities = SIX_HOURS_SKED_PRIORITY;
					break;
				}

				default: {
					// Unhandled case of classes
					if (!notYet.contains(courseCode)) {
						notYet.add(courseCode);
					}
					break;
				}
			}

			if (timePriorities != null) {
				switch (days) {
					case ONE_DAY: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks only from TUE to FRI
							for (int i = TUESDAY; i <= FRIDAY; i++) {
								final List<Integer> daysInt = new ArrayList<Integer>(
										Arrays.asList(i));
								if (this.isVacant(daysInt, timeInt)) {
									final SchedulePair pair = new SchedulePair(
											daysInt, timeInt);
									possibleSkeds.add(pair);
								}
							}
						}

						// also tries to find a schedule on Monday (less
						// priority
						// than
						// TUE to FRI)
						final List<Integer> daysInt = new ArrayList<Integer>(
								Arrays.asList(MONDAY));

						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					case TWO_DAYS: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks whether TUE and THU is vacant
							List<Integer> daysInt = new ArrayList<Integer>(
									Arrays.asList(TUESDAY, THURSDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}

							// checks whether WED and FRI is vacant
							daysInt = new ArrayList<Integer>(Arrays.asList(
									WEDNESDAY, FRIDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					case THREE_DAYS: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks whether MON, WED and FRI is vacant
							final List<Integer> daysInt = new ArrayList<Integer>(
									Arrays.asList(MONDAY, WEDNESDAY, FRIDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					case FOUR_DAYS: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks whether TUE, WED, THU and FRI are vacant
							final List<Integer> daysInt = new ArrayList<Integer>(
									Arrays.asList(TUESDAY, WEDNESDAY, THURSDAY,
											FRIDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					case FIVE_DAYS: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks whether MON TO FRI is vacant
							final List<Integer> daysInt = new ArrayList<Integer>(
									Arrays.asList(MONDAY, TUESDAY, WEDNESDAY,
											THURSDAY, FRIDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					default: {
						// Unhandled case of classes
						if (!notYet.contains(courseCode)) {
							notYet.add(courseCode);
						}
						break;
					}
				}
			}
		} else if (courseType == NSTP_COURSES) {
			// for NSTP COURSES

			switch (time) {
				case ONE_HOUR: {
					timePriorities = ONE_HOUR_LEC_WITH_LAB_SKED_PRIORITY;
					break;
				}

				case ONE_HOUR_HALF: {
					timePriorities = ONE_HOUR_HALF_LEC_SKED_PRIORITY;
					break;
				}

				case TWO_HOURS: {
					timePriorities = TWO_HOURS_SKED_PRIORITY;
					break;
				}

				case THREE_HOURS: {
					timePriorities = THREE_HOURS_SKED_PRIORITY;
					break;
				}

				case FIVE_HOURS: {
					timePriorities = FIVE_HOURS_SKED_PRIORITY;
					break;
				}

				case SIX_HOURS: {
					timePriorities = SIX_HOURS_SKED_PRIORITY;
					break;
				}

				default: {
					// Unhandled case of classes
					if (!notYet.contains(courseCode)) {
						notYet.add(courseCode);
					}
					break;
				}
			}

			if (timePriorities != null) {
				switch (days) {
					case ONE_DAY: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks only from TUE to FRI
							for (int i = TUESDAY; i <= FRIDAY; i++) {
								final List<Integer> daysInt = new ArrayList<Integer>(
										Arrays.asList(i));
								if (this.isVacant(daysInt, timeInt)) {
									final SchedulePair pair = new SchedulePair(
											daysInt, timeInt);
									possibleSkeds.add(pair);
								}
							}
						}

						// also tries to find a schedule on Monday (less
						// priority than
						// TUE to FRI)
						List<Integer> daysInt = new ArrayList<Integer>(Arrays
								.asList(MONDAY));

						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						// also tries to find a schedule on Saturday (less
						// priority than
						// MON to FRI)
						daysInt = new ArrayList<Integer>(Arrays
								.asList(SATURDAY));

						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					case TWO_DAYS: {
						for (final Iterator<int[]> skedIter = timePriorities
								.iterator(); skedIter.hasNext();) {
							final int[] timeInt = skedIter.next();

							// checks whether TUE and THU is vacant
							List<Integer> daysInt = new ArrayList<Integer>(
									Arrays.asList(TUESDAY, THURSDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}

							// checks whether WED and FRI is vacant
							daysInt = new ArrayList<Integer>(Arrays.asList(
									WEDNESDAY, FRIDAY));
							if (this.isVacant(daysInt, timeInt)) {
								final SchedulePair pair = new SchedulePair(
										daysInt, timeInt);
								possibleSkeds.add(pair);
							}
						}

						break;
					}

					default: {
						// Unhandled case of classes
						if (!notYet.contains(courseCode)) {
							notYet.add(courseCode);
						}
						break;
					}
				}
			}
		}

		if (possibleSkeds.size() < 1) {
			// No possible schedule found
			return null;
		} else {
			return possibleSkeds;
		}
	}

	/**
	 * Gets the string matrix schedule
	 * 
	 * @return the schedule
	 */
	public String[][] getSchedule() {
		return this.schedule;
	}

	/**
	 * Checks whether the schedule is vacant on the specified days and time
	 * 
	 * @param daysInt
	 * @param timeInt
	 * @return true if vacant, false otherwise
	 */
	public boolean isVacant(final List<Integer> daysInt, final int[] timeInt) {
		boolean isVacant = true;

		final int startTime = timeInt[0];
		final int endTime = timeInt[1];

		if (endTime > startTime) {
			for (int i = startTime; i <= endTime; i++) {
				for (final Iterator<Integer> dayIter = daysInt.iterator(); dayIter
						.hasNext();) {
					if (this.schedule[i][dayIter.next()] != null) {
						isVacant = false;
						break;
					}
				}

				if (!isVacant) {
					break;
				}
			}

		} else {
			System.out.println("ERROR: Time is not valid in isVacant()");
		}

		return isVacant;
	}

	/**
	 * Prints the string schedule matrix
	 */
	public void print() {
		for (int i = 0; i < 24; i++) {
			for (int j = 0; j < 6; j++) {
				System.out.print(this.schedule[i][j] + " | ");
			}
			System.out.println();
		}
	}

	/**
	 * Sets the string matrix schedule
	 * 
	 * @param schedule
	 *            the schedule to set
	 */
	public void setSchedule(final String[][] schedule) {
		this.schedule = schedule;
	}

}
